home *** CD-ROM | disk | FTP | other *** search
/ CD ROM Paradise Collection 4 / CD ROM Paradise Collection 4 1995 Nov.iso / program / swagd_f.zip / FAQ.SWG / 0013_Modem Reference.pas < prev    next >
Pascal/Delphi Source File  |  1993-08-17  |  21KB  |  460 lines

  1. From: JACK MOFFITT                 Refer#: NONE
  2.   To: ALL                           Recvd: NO
  3. Subj: MODEM REFERENCE       1/       Conf: (1221) F-PASCAL
  4. ---------------------------------------------------------------------------
  5.             Pascal Programmer's Reference to Modem Communications
  6.  
  7.                                    by
  8.  
  9.                                Jack Moffitt
  10.  
  11. ___-------------------------------------------------------------------------
  12.  
  13.  
  14. INTRODUCTION
  15. ~~~~~~~~~~~~
  16.         Direct UART programming is a subject that not many people are
  17. familiar with.  Since the advent of FOSSIL, many people advise that one
  18. should use that for all communications, to make it more portable.  But for
  19. some instances, it is necessary to have internal modem routines to go on.
  20. Because no one seems to know or understand this subject, and because I have
  21. found no other texts on the subject, I have decided to put it all into one
  22. text, and maybe round off the edges on this subject.
  23.  
  24.  
  25. THE ASYNCRONOUS MODEM
  26. ~~~~~~~~~~~~~~~~~~~~~
  27.         The asyncronous modem uses one (or more) specific ports on a
  28. computer, as well as an IRQ (Interrupt Request).  Every time a character
  29. of data is received in the device, an interrupt is processed.  One must
  30. make a interrupt service routine to handle this input, but where does it go?
  31. Since the IRQs are tied into interrupts, knowing the IRQ the device is using,
  32. we can replace that interrupt.  The port addresses and IRQ vectors are as
  33. follows:
  34.  
  35. Port Addresses: COM1  --  03F8h        IRQ Vectors   :  0  --  08h
  36.                 COM2  --  02F8h                         1  --  09h
  37.                 COM3  --  03E8h                         2  --  0Ah
  38.                 COM4  --  02E8h                         3  --  0Bh
  39.                                                         4  --  0Ch
  40.                                                         5  --  0Dh
  41. Standard Port IRQs: COM1  --  4                         6  --  0Eh
  42.                     COM2  --  3                         7  --  0Fh
  43.                     COM3  --  4                         8  --  70h
  44.                     COM4  --  3                         9  --  71h
  45.                                                        10  --  72h
  46.                                                        11  --  73h
  47.                                                        12  --  74h
  48.                                                        13  --  75h
  49.                                                        14  --  76h
  50.                                                        15  --  77h
  51.  
  52. For standard use, the IRQ for comm ports 1 and 3 is 4, and for 2 and 4 it's
  53. 3.  The 8250 UART has 10 registers available for getting, receiving and
  54. interperating data.  They are all located at offsets from the base address
  55. of the port.  Here are the registers and their offsets:
  56.  
  57. Register Offsets:  Transmitter Holding Register (THR)       --  00h
  58.                    Receiver Data Register (RDR)             --  00h
  59.                    Baud Rate Divisor Low Byte (BRDL)        --  00h
  60.                    Baud Rate Divisor High Byte (BRDH)       --  01h
  61.                    Interrupt Enable Register (IER)          --  01h
  62.                    Interrupt Identification Register (IIR)  --  02h
  63.                    Line Control Register (LCR)              --  03h
  64.                    Modem Control Register (MCR)             --  04h
  65.                    Line Status Register (LSR)               --  05h
  66.                    Modem Status Register (MSR)              --  06h
  67.  
  68. With this information one can address any register by adding the offset to
  69. the base address.  Therefor, if one is using COM2 (base address 02F8h) they
  70. would access the Modem Status Register with: port[$02F8 + $06].
  71.  
  72.  
  73. TRANSMITTER HOLDING REGISTER
  74. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  75.         This register contains the data to be sent to the remote PC or modem.
  76. When bit 5 (THR empty) of the LSR is set, one can write to this port, thus
  77. sending data over the phone line or null modem cable.
  78.  
  79.  
  80. RECEIVER DATA REGISTER
  81. ~~~~~~~~~~~~~~~~~~~~~~
  82.         This register contains the incoming data.  Read this register only
  83. if bit 0 (Data Received) of the LSR is set, otherwise one will get
  84. unpredictable characters.
  85.  
  86.  
  87. BAUD RATE DIVISOR
  88. ~~~~~~~~~~~~~~~~~
  89.         The Baud Rate Divisor is used to set the BPS rate.  To calculate the
  90. Baud Rate Divisor, one must use the formula: (UART Clock Speed)/(16*BPS).
  91. The UART Clock Speed is 1843200.  To set the BRD one must first set bit 7
  92. (port toggle) of the Line Control Register to 1, and then write the low and
  93. high bytes to the correct offsets.  Always remember to reset LCR bit 7 to 0
  94. after one is finished setting the BPS rate.
  95.  
  96.  
  97. INTERRUPT ENABLE REGISTER
  98. ~~~~~~~~~~~~~~~~~~~~~~~~~
  99.         The IER is used to simulate real interrupt calls.  Write a byte
  100. containing to interrupt information to enable any interrupts, all interrupts
  101. also have corresponding actions to clear the interrupts.  Here's the list:
  102.  
  103. Info Byte:
  104.  
  105. bit   7-6-5-4       3                 2                 1           0
  106.       ~~~~~~~       ~                 ~                 ~           ~
  107.      Always 0   MSR Change   Data Error or Break    THR empty  Data Received
  108.  
  109. To Clear:       Read MSR          Read LSR        Output to THR   Read RDR
  110.  
  111.  
  112. INTERRUPT IDENTIFICATION REGISTER
  113. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  114.         This register is used to determine what kind of interrupts have
  115. occured.  Read one byte from this register, and use AND masks to find out
  116. what has happened.  The information in the byte is:
  117.  
  118. Info Byte:
  119.  
  120. bit   7-6-5-4-3    2-1                                    0
  121.       ~~~~~~~~~    ~~~                                    ~
  122.        Unused      0-0 = Change in MSR                If this bit is set
  123.                    0-1 = THR empty                    more than one
  124.                    1-0 = Data Received                interrupt has
  125.                    1-1 = Data Error or Break          occured.
  126.  
  127.  
  128. LINE CONTROL REGISTER
  129. ~~~~~~~~~~~~~~~~~~~~~
  130.         The Line Control Register (LCR) is used for changing the settings
  131. on the serial line.  It is also used for initializing the modem settings.
  132. Write a byte to the port, containing the following info:
  133.  
  134. LCR Byte.
  135.  
  136.       Port Toggle   Break Condition   Parity      Stop Bits   Data Bits
  137. bit       7                6           5-4-3          2          1-0
  138.           ~                ~           ~~~~~          ~          ~~~
  139.           0 = Normal       0 = Off     0-0-0 = None   0 = 1      0-0 = 5
  140.           1 = Set BRD      1 = On      1-0-0 = Odd    1 = 2      0-1 = 6
  141.                                        1-1-0 = Even              1-0 = 7
  142.                                        1-0-1 = Mark              1-1 = 8
  143.                                        1-1-1 = Space
  144. Everything is pretty clear except for the purpose of bits 6 and 7.  Bit 6
  145. controls the sending of the break signal.  Bit 7 should always be 0, except
  146. if one is changing the baud rate.  Then one must set it to one, write to
  147. the BRD and then set it back to zero.  One can only write to the BRD if this
  148. bit is set.
  149.  
  150.  
  151. MODEM CONTROL REGISTER
  152. ~~~~~~~~~~~~~~~~~~~~~~
  153.         The MCR is used to control the modem and it's function.  Write one
  154. byte to the MCR containing the following info:
  155.  
  156. MCR Byte.
  157.  
  158. bit      0 = Set DTR Line
  159.          1 = Set RTS Line
  160.          2 = User Output #1
  161.          3 = User Output #2
  162.          4 = UART Loopback
  163.      7-6-5 = Unused (Set to 0)
  164.  
  165. Typically one will set bits 3 through 0 to 1.  Bit 4 is used for testing
  166. their routines without another modem, and the other bits are unused, but
  167. should always be set to 0.
  168.  
  169.  
  170. LINE STATUS REGISTER
  171. ~~~~~~~~~~~~~~~~~~~~
  172.         The LSR reports the current status of the RS232 serial line.  The
  173. information contained is obtained by reading one byte from the LSR.  The
  174. bits and the info associated with each are listed below.
  175.  
  176. LSR Byte.
  177.  
  178. bit     0 = Data Received
  179.         1 = Overrun Error
  180.         2 = Parity Error
  181.         3 = Framing Error
  182.         4 = Break Detect
  183.         5 = THR empty
  184.         6 = Transmit Shift Register (TSR) empty
  185.         7 = Time Out
  186.  
  187. The TSR takes the byte in the THR and transmits is one bit at a time.  When
  188. bit 0 is set one should read from the RDR, and when bit 5 is set one should
  189. write to the THR.  What actions are taken on various errors are left up to
  190. the programmer.
  191.  
  192.  
  193. MODEM STATUS REGISTER
  194. ~~~~~~~~~~~~~~~~~~~~~
  195.         Just like the LSR returns the status of the RS232 line, the MSR
  196. returns the status of the modem.  As with other registers, each bit in the
  197. byte one reads from this port contains a certain piece of info.
  198.  
  199. MSR byte.
  200.  
  201. bit     0 = Change in CTS
  202.         1 = Change in DSR
  203.         2 = Change in RI
  204.         3 = Change in DCD
  205.         4 = CTS on
  206.         5 = DSR on
  207.         6 = RI on
  208.         7 = DCD on
  209.  
  210. Carrier Detect is achieved by testing bit 7, to see if the line is ringing
  211. test bit 6.
  212.  
  213.  
  214.  
  215. PUTTING IT ALL TOGETHER
  216. ~~~~~~~~~~~~~~~~~~~~~~~
  217.         One can now use this information about the 8250 UART to start
  218. programming their own modem routines.  But before they can do that, they
  219. must learn a little about interrupts and the 8259 PIC (Programmable
  220. Interrupt Controller).  This information is necessary to write modem
  221. routines that are not dependant on a slow BIOS.
  222.  
  223.  
  224. INTERRUPTS
  225. ~~~~~~~~~~
  226.         Interrupts are a broad subject, and this is not a reference for them.
  227. For for information on interrupts, one should look at DOS Programmer's
  228. Reference 4th Edition.  Although there are two kinds of interrupts - Non-
  229. Maskable and Maskable, maskable interrupts are the only ones that one should
  230. be concerned with.  When an interrupt generates, the processor finishes the
  231. current command, and then saves a few variables (the address to return to)
  232. on the stack and jumps to the vector of the interrupt.  One can turn off
  233. maskable interrupts with the STI, and back on with CLI.  One can not turn
  234. off non-maskable interrupts.  Replacing interrupt routines in pascal is very
  235. easy.  Include the DOS unit in their program, and use the procedures GetIntVec
  236. and SetIntVec.  To replace the interrupt for COM2 (remember it's 0Bh) one
  237. would do this:
  238.                GetIntVec($0B, OldInt0Bh);
  239.                SetIntVec($0B, NewInt0Bh);
  240. At the end of the program, one MUST restore the interrupt using:
  241.                SetIntVec($0B, OldInt0Bh);
  242. Failing to do this will most likely result in a system crash after the
  243. program terminates.  Because another interrupt may be called inside another
  244. interrupt at any time, it is necessary to turn off interrupts, as mentioned
  245. above, every once in a while.  Remember all this, and programming for the
  246. modem will be much easier ( :) ).
  247.  
  248.  
  249. 8259 PROGRAMMABLE INTERRUPT CONTROLLER
  250. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  251.         The 8259 PIC is used by the processor as a gateway for interrupts.
  252. The 8259 decides which interrupts go first and which are currently active.
  253. The order interrupts are processed in in the order of their IRQ number.
  254. Thus, IRQ0 will always be processed before IRQ1 if both are generated at the
  255. same time.  Since asyncronous communication uses IRQs, we must instruct the
  256. 8259 PIC on when are interrupts should start interrupting, and when they
  257. should stop.  When initializing the modem, one must "turn on" the IRQ before
  258. one can start to use it.  Turning back off is identical, but don't turn it
  259. off if one is writing door routines!  To do either requires one assign the
  260. value contained at the port the value AND the mask.  The masks for turning
  261. on and off the 8259 follows.
  262.  
  263. To Turn On:
  264.             mask = (1 shl (IRQ number)) xor $00FF
  265. To Turn Off:
  266.              mask = 1 shl (IRQ number)
  267.  
  268. One must also reset the PIC in the custom interrupt handler after one is
  269. finished with it.  That will allow the PIC to process the next interrupt.
  270. To reset the PIC, write 20h to it.  This is also refered to as the End Of
  271. Interrupt (EOI) signal.  This must also be done after first initializing the
  272. modem.  There is another PIC on the 286, allowing the last 8 IRQs (7 - 15).
  273. The second PIC is called the cascade PIC.  The addresses for the PIC command
  274. and mask ports are listed next.
  275.  
  276. 8259 PIC command address         = 20h
  277. 8259 PIC mask address            = 21h
  278. Cascade 8259 PIC command address = A0h
  279. Cascade 8259 PIC mask address    = A1h
  280.  
  281. To reset the PIC always write to the command, and for turning off with the
  282. masks always write to the mask.  The masks for the cascade PIC are the same
  283. for the other PIC.  So the mask for IRQ0 is equal to the mask for IRQ7.
  284. Also, one should write 20h to the cascade PIC as the EOI signal.
  285.  
  286.  
  287. INPUT/OUTPUT CONTROL
  288. ~~~~~~~~~~~~~~~~~~~~
  289.         To keep the text simple, only buffered input will be covered.
  290. Buffered output is a subject of more depth than one can provide in a short
  291. reference.  Buffered input is relatively simple, but there are a few things
  292. one must consider.  The size of the buffer is very import, make the buffer
  293. to big and one will eat up the datasegment, make the buffer to small and
  294. one will get overruns.  A good choice for a general buffer would be in the
  295. range of 4 to 8k.  This should allow plenty of room for all incoming data.
  296. Another inportant factor is the type of buffer.  For simplicity and ease of
  297. use, a circular input buffer is recommended.  A head and a tail point
  298. to the start and end of the buffer, and they will both wrap around when
  299. either go past the end of the buffer, thus making the buffer a kind of
  300. circle.  Getting data in the buffer is the primary job of the custom
  301. interrupt routine.  Clearing the buffer and reading characters from the
  302. buffer is then as easy as reading a character from an array, and advancing
  303. the head of the buffer.  Sending characters over the phone can be
  304. accomplished by waiting for the flow control and then sending the character
  305. to the THR, repeating for every character.
  306.  
  307. THE INTERRUPT SERVICE ROUTINE
  308. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  309.         The ISR (Interrupt Service Routine) is the backbone for asyncronous
  310. communication.  The interrupt is called for every character that comes
  311. through the modem.  So in the interrupt one must process these incoming
  312. characters or else they will be lost.  Since the the interrupt got called,
  313. one must check the IIR (Interrupt Identification Register) to see what
  314. actually cause the interrupt to be called.  Since the interrupt is mainly
  315. dealing with handling the incoming data, and for reasons of simplicity,
  316. flow control will be ommited from the routine but will be discussed later
  317. in this text.  Since one is writing to the buffer, and since another
  318. character is likely to come in during this time, one must disable interrupts
  319. for the shortest time possible while writing to the buffer, and then reenable
  320. them so no data is lost.  (NOTE: If the ISR is to be contained in a unit, it
  321. must be declared in the unit's interface section as an INTERRUPT procedure.)
  322. After disabling interrupts, checking for data, discarding data if no buffer
  323. space is available, putting the data in the buffer if there is room, and
  324. clearing the RDR if any data error or break occured, one must turn on the
  325. interrupts and issue the EOI signal to the 8259 PIC or both the 8259 PIC
  326. and the cascade PIC if IRQ7 - IRQ15 is used.  Here is a sample routine:
  327.  
  328.  
  329. const
  330.   BaseAddr: array[1 .. 4] of word = ($03F8, $02F8, $03E8, $02E8);
  331.   { Nice array to make finding the base address easy }
  332.  
  333. var
  334.   Buffer: array[1 .. 4096] of char;  { A 4k buffer for input }
  335.   Temp,  { Varible to hold various modem statuses }
  336.   CommPort: byte;  { Comm Port in use }
  337.   Head,  { Start of the buffer }
  338.   Tail,  { End of the buffer }
  339.   Size: word;  { Size of the buffer }
  340.   Cascade: boolean;  { For IRQ7 - IRQ15 }
  341.  
  342. procedure Async_ISR; interrupt; { NOTE: must declare the procedure interrupt }
  343. begin
  344.   inline($FB); { STI - Disable interrupts }
  345.   Temp := port[BaseAddr[CommPort] + $02];  { Read a byte from the IIR }
  346.   if Temp and $06 = $04 then  { Character received }
  347.   begin
  348.     if Head <> Tail then  { Make sure there is room in the buffer }
  349.     begin
  350.       Buffer[Tail] := Chr(port[BaseAddr[CommPort] + $00]);  { Read char }
  351.       inc(Tail);  { Position the Tail for the next char }
  352.       if Tail > 4096 then Tail := 0;  { If Tail is greater, wrap the buffer }
  353.     end
  354.     else temp := port[BaseAddr[CommPort] + $00];  { Throw away overruns }
  355.   end
  356.   else if Temp and $06 = $06 then  { Data error or break }
  357.     Temp := port[BaseAddr[CommPort] + $00];  { Clear RDR }
  358.   inline($FA);  { CLI - Enable interrupts }
  359.   port[$20] := $20;  { Reset the 8259 PIC }
  360.   if Cascade then port[$A0] := $20;  { Reset the cascade PIC }
  361. end;
  362.  
  363.  
  364. First the procedure disables interrupts, then it reads the IIR to find out
  365. what kind of interrupt needs processing.  The procedure then masks out bits
  366. 2 and 1 and tests it to see if bit 4 is set.  If data is received it checks
  367. to make sure there is room in the buffer, and places the character at the
  368. position marked by Tail, otherwise it disregards the character as overrun.
  369. If a data error occured it clears the RDR to make sure no garbage is
  370. received.  Finally it enables interrupts and resets the 8259 (and the cascade
  371. if necessary).
  372.  
  373.  
  374. SENDING CHARACTERS
  375. ~~~~~~~~~~~~~~~~~~
  376.         Sending character over the modem is much simpler than getting them.
  377. First one must wait for the flow control and for the UART and then write the
  378. character to the THR.  Here's an example:
  379.  
  380. procedure XmitChar(C: char);  { Uses variable and constant declarations from
  381. begin                           the previous example }
  382.   while ((port[BaseAddr[CommPort] + $05] and $20 <> $20) and  { Wait for THR }
  383.          (port[BaseAddr[CommPort] + $06] and $10 <> $10))  { Wait for CTS }
  384.   do ;  { Do nothing until CTS and THR empty }
  385.   port[BaseAddr[CommPort] + $00] := Ord(C);  { Send character }
  386. end;
  387.  
  388. This waits for the CTS signal and for the THR to be clear and then sends the
  389. character.  To send strings just use this in a repeat loop such as:
  390.  
  391. for x := 1 to length(s) do
  392.   XmitChar(s[x]);
  393.  
  394.  
  395. READING CHARACTERS
  396. ~~~~~~~~~~~~~~~~~~
  397.         The actual reading of character takes place in the ISR, but one still
  398. has to get them from the buffer.  Just read the character at the head of
  399. the buffer and pass it back.  An example:
  400.  
  401. function RemoteReadKey: char;  { Uses var and const from above }
  402. begin
  403.   RemoteReadKey := Buffer[Head];  { Get the character }
  404.   inc(Head);  { Move Head to the next character }
  405.   if Head > 4096 then Head := 0;  { Wrap Head around if necessary }
  406.   dec(Size);  { Remove the character }
  407. end;
  408.  
  409. To find out if a character is waiting is even easier:
  410.  
  411. function RemoteKeyPressed: boolean;  { Uses vars and consts from above }
  412. begin
  413.   RemoteKeyPressed := Size > 0;  { A key was pressed if there is data in
  414. end;                               the buffer }
  415.  
  416.  
  417. INITIALIZING MODEM PARAMETERS AND OTHER TOPICS
  418. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  419.         For most cases one can use interrupt 14h function 00h to initialize
  420. modem parameters, but if the baud rate is over 9600, this function will
  421. not work.  One must change the BRD themselves.  It is a simple matter of
  422. accessing the BRD by setting the LCR bit 7 to 1 and writing to the BRD and
  423. then reseting the LCR bit 7 back to 0.  Everything else, clearing buffers,
  424. flushing buffers, formatting input, is all up to the programmer.  I have
  425. provided one with enough information to grasp the basis of modem programming
  426. and the I/O involved.
  427.  
  428. FLOW CONTROL
  429. ~~~~~~~~~~~~
  430.         Flow control is mainly used to prevent overflow error on today's
  431. high speed modems.  CTS/RTS was already covered earlier, but nothing has
  432. been said for XOn/XOff.  XOn/XOff will send a certain character (usually
  433. a ^S) when the input buffer has reached a certain percentage of capacity.
  434. This signal is XOff.  When the buffer has gone down to another percentage of
  435. capacity, XOn (usually a ^Q) will be sent.  It is the programmer's job to
  436. look for XOn/XOff codes and interperate them, as there are no standard ways
  437. to do it as with CTS/RTS.  It is also his job to make sure he or she sends
  438. the signals at the appropriate time.
  439.  
  440.  
  441. CONCLUSION
  442. ~~~~~~~~~~
  443.         This text is general, and won't satisfy the needs of advanced modem
  444. programmers.  It was written to help those just starting, or thinking about
  445. starting, through the ordeal of finding a book, or read through source not
  446. knowing what some of it does.  If one finds any mistakes, please feel free
  447. to contact me via the Pascal FIDONet echo, and he will gladly correct
  448. them.  Also, if one would like more information on other related topics,
  449. contact me via the Pascal echo, and I will try to help.
  450.  
  451. _____________________________________________________----
  452.  
  453.  
  454. I hope everyone will find this text useful, and please feel free to
  455. comment or correct anything.  I posted it once but it got choped off in
  456. places, so i'm posting it again.  Enjoy.
  457.  
  458.         Jack
  459.  
  460.